
/**
 * Aurora
 * 场景部分
 */

import message from './message';
import monitor from './monitor';
import fetcher from './fetcher';
import Danmaku from '../assets/danmaku';
import Save from '../assets/save';

/* **********
 *   场景   *
 ********** */

/**
 * 场景类
 */
class Scene {
  /**
   * 用 SceneProto 创建 Scene
   * @param proto
   *   SceneProto 实例
   */
  constructor(proto) {
    for (let key in proto) {
      this[key] = proto[key];
    }
  }

  require = [];
  init (libs) {}
  destroy () {}

  // 需要子类覆盖的
  collectInfo () {
    return null;
  }
  // const / final 方法
  sendStatus () {
    sendSceneStatus(this);
  }
  // 下面的需要子类覆盖的
  onMessage (key, data) {
    /* do nothing */
  }
  onTick (assetId, data) {
    /* do nothing */
  }
};

/**
 * 场景名 - 场景创建工厂方法 factory
 * key   : `${series}/${scene}`
 * value : function factory (arg|undefined) : Scene
 */
var sceneMap = {};

/**
 * 挂件名 - Pendant 工厂方法
 * key   : `${series}/${pendant}`
 * value : function factory (arg|undefined) : Pendant
 */
var pendantMap = {};

/**
 * 当前正在播放的场景. Scene 实体. (不是 SceneProto)
 * @type {Scene}
 */
var curScene = null;
/**
 * 当前正在播放的场景的名字 `${series}/${scene}`
 * @type {string}
 */
var curSceneName = null;

function getCurrentScene() {
  return curScene;
};

function getCurrentSceneName() {
  return curSceneName;
};

function getCurrentSeriesName() {
  return splitSceneName(curSceneName).seriesName;
};

message.subscribe('screen-command', function (event, args) {
  curScene.onMessage(args.name, args.arg);
});

message.subscribe('screen-ticked', function (event, args) {
  curScene.onTick(args.assetId, args.data);
});

/**
 * 添加场景工厂函数
 * @param {string} sceneFullName 格式 `${series}/${scene}`
 * @param {function (any) : Scene} factory 
 */
function addSceneFactory(sceneFullName, factory) {
  if (typeof(factory) === 'function' && sceneFullName) {
    sceneMap[sceneFullName] = factory;
  } else {
    console.error('无法添加场景工厂函数', sceneFullName, factory);
  }
}

/**
 * @param {string} sceneFullName 
 */
function loadScene(sceneFullName) {
  let {seriesName, sceneName} = splitSceneName(sceneFullName);

  return fetcher.allSeries()
    .then(a => {
      let array = a.data[seriesName].scenes;
      if (array == null) {return null;}
      for (let i = 0; i < array.length; i++) {
        if (array[i].name == sceneName) {return array[i].jsPath;}
      }
      return null;
    })
    .then((jsPath) => {
      if (jsPath == null) return null;
      jsPath = jsPath.endsWith('.js') ? jsPath : jsPath + '.js';
      // 从远端获取 js
      return fetcher.fetchComponentScript(sceneFullName, jsPath)
        .then((res) => {
          let [ js, manifest ] = res;
          if (js.startsWith('var ')) {
            js = js.substring(4);
          }
          // 2021-05 之后入口改为 `./scene/${seriesName}/${jsEntry}` 例如 "./scene/danmaku/show/danmaku_app.js"
          return loadModule(js, manifest, `./scene/${seriesName}/${jsPath}`);
        });
    })
    .then((factory) => {
      sceneMap[sceneFullName] = factory;
      return factory;
    });
}

/**
 * 启动 scene. 并向服务器发送状态变化的消息
 * @param {string} sceneFullName 格式 `${series}/${scene}`, 不能为 null
 * @param {string} initParam
 */
function startScene(sceneFullName, initParam) {
  let {seriesName} = splitSceneName(sceneFullName);
  let factory = sceneMap[sceneFullName];
  /** @type {Promise} */
  let promise;

  if (!factory) {
    promise = loadScene(sceneFullName);
  } else {
    promise = Promise.resolve(factory);
  }

  return promise.then((factory) => {
    curScene = factory(new Context(seriesName, initParam));
    curSceneName = sceneFullName;
    return curScene;
  })
  .then((scene) => {
    if (scene.require == null || scene.require.length == 0) {
      return {scene, libs : {}};
    }

    // 加载依赖项
    // scene.require = ['zrender']
    // 这里需要把 exports 放到 'zrender' 键对应的值上去
    return fetcher.requireLibrary(scene.require)
      .then((libs) => {
        let obj = {};
        for(let i = 0; i < libs.length; i++) {
          obj[scene.require[i]] = libs[i];
        }
        return {scene, libs : obj};
      });
  })
  .then(obj => {
    let {scene, libs} = obj;

    // 初始化. require 指定的加载数据惟一一次注入
    scene.init(libs);

    // 向服务器发送播放状态的消息
    sendSceneStatus(scene);
  })
}

/**
 * 发送指定场景的状态
 * @param {Scene | null} scene 默认为当前场景
 */
function sendSceneStatus(scene) {
  let {seriesName, sceneName} = splitSceneName(curSceneName);
  scene = scene || curScene;

  lastSendTimestamp = Date.now();

  monitor.sendStatus({
    status : 'playing',
    scene : sceneName,
    series : seriesName,
    remark : scene.collectInfo(),
    time : lastSendTimestamp, // 补充预期发送时间, 看看浏览器是不是有延迟发送的可能性
  });
}

/*
 * 注意, 新规则中要求至少每 10 分钟发送一次当前状态
 */
/** @type {number} 上一次发送状态的时间点 */
let lastSendTimestamp = null;

setInterval(() => {
  if (lastSendTimestamp == null) {
    return;
  }

  let now = Date.now();
  if (now - lastSendTimestamp > 10 * 60 * 1000) {
    sendSceneStatus();
  }
}, 10000);


/* **********
 *   挂件   *
 ********** */

class Pendant {
  constructor(proto) {
    for (let key in proto) {
      this[key] = proto[key];
    }
  };
  require = [];
  init(libs, args) {};
  destroy() {};
  // TODO
  finished() {
    destroyPendant(this);
  }
};

/**
 * 当前还活跃着的挂件
 */
let activePendants = [];

/**
 * 创建一个挂件
 * @param {string} pendantName 挂件名 `${series}/${pendant}`
 * @param arg 挂件的其它启动参数 any
 * @return Promise(Pendant)
 */
function createPendant(pendantFullName, args) {
  let {seriesName} = splitPendantName(pendantFullName);
  let factory = pendantMap[pendantFullName];

  /** @type {Promise} */
  let promise;
  if (!factory) {
    promise = loadPendant(pendantFullName);
  } else {
    promise = Promise.resolve(factory);
  }

  // Promise 初始化, 基本同 scene
  return promise
    .then((factory) => factory(new Context(seriesName)))
    .then(function (pendant) {
      if (pendant.require == null || pendant.require.length == 0) {
        return {pendant, libs : {}};
      }

      // 加载依赖项
      // pendant.require = ['zrender']
      // 这里需要把 exports 放到 'zrender' 键对应的值上去
      return fetcher.requireLibrary(pendant.require)
        .then((libs) => {
          let obj = {};
          for(let i = 0; i < libs.length; i++) {
            obj[pendant.require[i]] = libs[i];
          }
          return {pendant, libs : obj};
        });
    }).then(obj => {
      let {pendant, libs} = obj;

      // 初始化. require 指定的加载数据惟一一次注入
      pendant.init(libs, args);
      activePendants.push({
        name : pendantFullName,
        pendant : pendant
      });

      // 向服务器发送播放状态的消息
      return pendant;
    });
};

/**
 * @param {Pendant} pendant 
 */
function destroyPendant(pendant) {
  let index = -1;
  let pendantItems = activePendants.find((item, index0) => {
    if (item.pendant == pendant) {
      index = index0;
      return true;
    }
    return false;
  });

  try {
    pendant.destroy();
  } catch (e) {
    console.error(e);
  }

  if (index < 0) {
    console.error('activePendants 统计出现问题', activePendants);
    return;
  } else {
    activePendants.splice(index, 1);
    message.release('pendant-destroyed', pendantItems[0]);
  }
};

/**
 * 导入挂件数据
 * @param {string} pendantFullName 挂件名
 * @return Promise(Pendant 的工厂方法)
 */
function loadPendant(pendantFullName) {
  let {seriesName, pendantName} = splitPendantName(pendantFullName);

  return fetcher.allSeries()
    .then(a => {
      let array = a.data[seriesName].pendants;
      if (array == null) {return null;}
      for (let i = 0; i < array.length; i++) {
        if (array[i].name == pendantName) {return array[i].jsPath;}
      }
      return null;
    })
    .then((jsPath) => {
      if (jsPath == null) return null;
      jsPath = jsPath.endsWith('.js') ? jsPath : jsPath + '.js';
      // 从远端获取 js
      return fetcher.fetchComponentScript(pendantFullName, jsPath)
        .then((res) => {
          let [ js, manifest ] = res;
          if (js.startsWith('var ')) {
            js = js.substring(4);
          }
          // 2021-05 之后入口改为 `./scene/${seriesName}/${jsEntry}` 例如 "./scene/danmaku/show/danmaku_app.js"
          return loadModule(js, manifest, `./scene/${seriesName}/${jsPath}`);
        });
    })
    .then((factory) => {
      pendantMap[pendantFullName] = factory;
      return factory;
    });
};

// 工具方法

function splitSceneName(name) {
  let index = name.indexOf('/');
  let seriesName = name.substring(0, index);
  let sceneName = name.substring(index + 1);
  return {seriesName, sceneName};
}
function splitPendantName(name) {
  let index = name.indexOf('/');
  let seriesName = name.substring(0, index);
  let pendantName = name.substring(index + 1);
  return {seriesName, pendantName};
}

/**
 * @param {string} js 
 * @param {object} manifest 
 *   示例是: {"name":"chunk","content":{"${jsEntry}":{"id":7,...}}}
 * @param {string} jsEntry
 *   是个路径格式, 比如 "./scene/flight/pendant/moon_record.js"
 * @returns {function | null}
 */
function loadModule(js, manifest, jsEntry) {
  var chunk = null;
  eval(js); // 这里, chunk 就不为 null 了

  /*
   * 按 development 方式打包, chunk 的获取方式是路径;
   * 按 production 方式则是自定义的数值, 只能靠 manifest 来读取: 如果没有 manifest 则不能以这个方式读
   */
  let factory;
  if (manifest == null) {
    let m = chunk(jsEntry); // throwable
    factory = m.default == null ? m : m.default;
  } else {
    let realEntry = manifest.content[jsEntry].id;
    let m = chunk(realEntry);
    factory = m.default == null ? m : m.default;
  }

  if (factory == null) {
    console.error(`无法获取 ${jsEntry} 对应的创建函数. 请检查 scene 和 pendent 的 webpack.dll.js 路径`);
    throw jsEntry;
  }
  if (typeof(factory) == 'object') {
    console.error(`无法获取 ${jsEntry} 对应的创建函数. 你的 scene 和 pendent 是否忘记 export?`);
    throw jsEntry;
  }

  return factory;
}


/**
 * Context
 */
class Context {
  /** @type {string} */
  seriesName = null;
  // 类
  Scene = Scene;
  Pendant = Pendant;
  // 挂件部分
  destroyPendant = destroyPendant;
  createPendant = createPendant;
  // 事件监听部分
  subscribeEvent = message.subscribe;
  subscribeOne = message.subscribeOne;
  releaseEvent = message.release;
  removeSubscriber = message.remove;
  // 发送信息部分 (Event)
  sendAttachAssetEvent = monitor.sendAttachAssetEvent;
  sendDetachAssetEvent = monitor.sendDetachAssetEvent;
  // 资源
  Danmaku = Danmaku;
  Save = Save;
  // 其它 TODO
  getCurrentScene = getCurrentScene;
  assetPath = (path) => fetcher.assetPath(this.seriesName, path);
  commonAssetPath = (path) => fetcher.assetPath('common', path);

  parseInitParam = () => parseInitParam(this.initParam);

  /**
   * @param {string} seriesName 
   * @param {string} initParam 
   */
  constructor(seriesName, initParam = '') {
    this.seriesName = seriesName;
    this.initParam = initParam;
  }
}

/**
 * @param {string} text 
 */
function parseInitParam(text) {
  text = text.trimLeft() + ' ';
  /**
   * status = 0: 需要确定 key, 查找 '='
   * status = 1: 查找到 '=', 需要确定 value, 查找 ' '
   * status = 2: 查找到 ' ', 需要确定非 ' ', 查找下一 K-V 对
   * 
   * mark 是看到 '\' 或者引号的确定边界, 这个先不管 TODO
   */
  let obj = {}, status = 2, index = 0;
  let key = '';
  // let marks = null;
  for (let i = 0; i < text.length; i++) {
    let ch = text[i];
    switch (status) {
      case 0: {
        if (ch == '=') {
          key = text.substring(index, i);
          index = i + 1;
          status = 1;
        } else if (ch == ' ') {
          obj[text.substring(index, i)] = '';
          status = 2;
        }
      } break;
      case 1: {
        if (ch == ' ') {
          obj[key] = text.substring(index, i);
          status = 2;
        }
      } break;
      case 2: {
        if (ch != ' ') {
          index = i;
          status = 0;
        }
      } break;
    }
  }
  return obj;
}

export default {
  Scene, Pendant, Context,

  // scene
  addSceneFactory, startScene, sendSceneStatus, getCurrentScene,
  getCurrentSceneName, getCurrentSeriesName,

  // pendant
  createPendant, destroyPendant
};
